package cn.xtits.xtf.common.db;

import org.apache.commons.lang3.StringUtils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.*;
import java.util.Date;

/**
 * Created by ShengHaiJiang on 2016/4/6.
 */
public class BeanConverter {
    private static final Map<Class<?>, Object> primitiveDefaults = new HashMap();
    private static final String EXCLUDE_CHAR ="_";
//    private static final int FIELD_PREFIX_LENGTH = 2;


    public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {
        PropertyDescriptor[] props = this.propertyDescriptors(type);
        ResultSetMetaData rsmd = rs.getMetaData();
        int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
        return this.createBean(rs, type, props, columnToProperty);
    }

    public <T> List<T> toBeanList(ResultSet rs, Class<T> type) throws SQLException {
        ArrayList results = new ArrayList();
        if(!rs.next()) {
            return results;
        } else {
            PropertyDescriptor[] props = this.propertyDescriptors(type);
            ResultSetMetaData rsmd = rs.getMetaData();
            int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);

            do {
                results.add(this.createBean(rs, type, props, columnToProperty));
            } while(rs.next());

            return results;
        }
    }

    private <T> T createBean(ResultSet rs, Class<T> type, PropertyDescriptor[] props, int[] columnToProperty) throws SQLException {
        T bean = this.newInstance(type);

        for(int i = 1; i < columnToProperty.length; ++i) {
            if(columnToProperty[i] != -1) {
                PropertyDescriptor prop = props[columnToProperty[i]];
                Class propType = prop.getPropertyType();
                Object value = null;
                if(propType != null) {
                    value = this.processColumn(rs, i, propType);
                    if(value == null && propType.isPrimitive()) {
                        value = primitiveDefaults.get(propType);
                    }
                }

                this.callSetter(bean, prop, value);
            }
        }

        return bean;
    }

    private void callSetter(Object target, PropertyDescriptor prop, Object value) throws SQLException {
        Method setter = prop.getWriteMethod();
        if(setter != null) {
            Class[] params = setter.getParameterTypes();

            try {
                if(value instanceof Date) {
                    String e = params[0].getName();
                    if("java.sql.Date".equals(e)) {
                        value = new java.sql.Date(((Date)value).getTime());
                    } else if("java.sql.Time".equals(e)) {
                        value = new Time(((Date)value).getTime());
                    } else if("java.sql.Timestamp".equals(e)) {
                        Timestamp tsValue = (Timestamp)value;
                        int nanos = tsValue.getNanos();
                        value = new Timestamp(tsValue.getTime());
                        ((Timestamp)value).setNanos(nanos);
                    }
                } else if(value instanceof String && params[0].isEnum()) {
                    value = Enum.valueOf(params[0].asSubclass(Enum.class), (String)value);
                }

                if(this.isCompatibleType(value, params[0])) {
                    setter.invoke(target, new Object[]{value});
                } else {
                    throw new SQLException("Cannot set " + prop.getName() + ": incompatible types, cannot convert " + value.getClass().getName() + " to " + params[0].getName());
                }
            } catch (IllegalArgumentException var9) {
                throw new SQLException("Cannot set " + prop.getName() + ": " + var9.getMessage());
            } catch (IllegalAccessException var10) {
                throw new SQLException("Cannot set " + prop.getName() + ": " + var10.getMessage());
            } catch (InvocationTargetException var11) {
                throw new SQLException("Cannot set " + prop.getName() + ": " + var11.getMessage());
            }
        }
    }

    private boolean isCompatibleType(Object value, Class<?> type) {
        return value != null && !type.isInstance(value)?(type.equals(Integer.TYPE) && value instanceof Integer?true:(type.equals(Long.TYPE) && value instanceof Long?true:(type.equals(Double.TYPE) && value instanceof Double?true:(type.equals(Float.TYPE) && value instanceof Float?true:(type.equals(Short.TYPE) && value instanceof Short?true:(type.equals(Byte.TYPE) && value instanceof Byte?true:(type.equals(Character.TYPE) && value instanceof Character?true:type.equals(Boolean.TYPE) && value instanceof Boolean))))))):true;
    }

    protected <T> T newInstance(Class<T> c) throws SQLException {
        try {
            return c.newInstance();
        } catch (InstantiationException var3) {
            throw new SQLException("Cannot create " + c.getName() + ": " + var3.getMessage());
        } catch (IllegalAccessException var4) {
            throw new SQLException("Cannot create " + c.getName() + ": " + var4.getMessage());
        }
    }

    private PropertyDescriptor[] propertyDescriptors(Class<?> c) throws SQLException {
        BeanInfo beanInfo = null;

        try {
            beanInfo = Introspector.getBeanInfo(c);
        } catch (IntrospectionException var4) {
            throw new SQLException("Bean introspection failed: " + var4.getMessage());
        }

        return beanInfo.getPropertyDescriptors();
    }

    protected int[] mapColumnsToProperties(ResultSetMetaData rsmd, PropertyDescriptor[] props) throws SQLException {
        int cols = rsmd.getColumnCount();
        int[] columnToProperty = new int[cols + 1];
        Arrays.fill(columnToProperty, -1);

        for(int col = 1; col <= cols; ++col) {
            String columnName = rsmd.getColumnLabel(col);
            if(StringUtils.isEmpty(columnName)) {
                columnName = rsmd.getColumnName(col);
            }
            String propertyName = columnName.replaceAll(EXCLUDE_CHAR, "");

//            String propertyName = columnName.toLowerCase();
//            if(propertyName.startsWith(FIELD_PREFIX)){
//                propertyName = propertyName.substring(FIELD_PREFIX_LENGTH);
//            }

            for(int i = 0; i < props.length; ++i) {
                if(propertyName.equalsIgnoreCase(props[i].getName())) {
                    columnToProperty[col] = i;
                    break;
                }
            }
        }

        return columnToProperty;
    }

    protected Object processColumn(ResultSet rs, int index, Class<?> propType) throws SQLException {
        return !propType.isPrimitive() && rs.getObject(index) == null?null:(propType.equals(String.class)?rs.getString(index):(!propType.equals(Integer.TYPE) && !propType.equals(Integer.class)?(!propType.equals(Boolean.TYPE) && !propType.equals(Boolean.class)?(!propType.equals(Long.TYPE) && !propType.equals(Long.class)?(!propType.equals(Double.TYPE) && !propType.equals(Double.class)?(!propType.equals(Float.TYPE) && !propType.equals(Float.class)?(!propType.equals(Short.TYPE) && !propType.equals(Short.class)?(!propType.equals(Byte.TYPE) && !propType.equals(Byte.class)?(propType.equals(Timestamp.class)?rs.getTimestamp(index):(propType.equals(SQLXML.class)?rs.getSQLXML(index):rs.getObject(index))):Byte.valueOf(rs.getByte(index))):Short.valueOf(rs.getShort(index))):Float.valueOf(rs.getFloat(index))):Double.valueOf(rs.getDouble(index))):Long.valueOf(rs.getLong(index))):Boolean.valueOf(rs.getBoolean(index))):Integer.valueOf(rs.getInt(index))));
    }

    static {
        primitiveDefaults.put(Integer.TYPE, Integer.valueOf(0));
        primitiveDefaults.put(Short.TYPE, Short.valueOf((short)0));
        primitiveDefaults.put(Byte.TYPE, Byte.valueOf((byte)0));
        primitiveDefaults.put(Float.TYPE, Float.valueOf(0.0F));
        primitiveDefaults.put(Double.TYPE, Double.valueOf(0.0D));
        primitiveDefaults.put(Long.TYPE, Long.valueOf(0L));
        primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
        primitiveDefaults.put(Character.TYPE, Character.valueOf('\u0000'));
    }
}
